//
// Created by yunnan xu on 2020/11/22.
//

#include "Scene.h"
#include <utility>
#include "Camera.h"
#include "EngineCore.h"
#include "MobileInput.h"
#include "tinyxml2.h"
#include "ResourceMgr.h"
#include "CPPUtils.h"
#include "RenderItem.h"
#include "Texture.h"
#include "Model.h"
#include "glm.hpp"
#include "gtx/transform.hpp"
#include "GJK.h"
#include "PhyScene.h"
#include "RigidBody.h"
#include "RenderItem.h"
#include "RenderItemStaticOpaque.h"
#include "RenderItemOpaque.h"
#include "RenderItemSkinnedMeshOpaque.h"
#include "RenderItemInstanced.h"
#include "AABB.h"
#include "ParticleSystem.h"
#include "SkinnedMeshModel.h"

Scene::Scene(const char * scene_file_path)
{
    long file_size = ResourceMgr::GetInstance()->GetFileLength(scene_file_path, AASSET_MODE_BUFFER);
    char * scene_file_content = new char [file_size];

    int read_res = ResourceMgr::GetInstance()->ReadFile(scene_file_path, scene_file_content, file_size, AASSET_MODE_BUFFER);
    if (read_res < 0)
    {
        LOGE("Texture2D ReadFile error filepath = %s, res = %d", scene_file_path, read_res);
        return;
    }
    else {
        LOGI("Texture2D filepath = %s, read_res = %d", scene_file_path, read_res);
    }
    xmlDoc.Parse(scene_file_content, file_size);

    phyScene = std::make_shared<Phy::PhyScene>();
    particleSystem = std::make_shared<ParticleSystem>();
}

void Scene::ParseScene()
{
    tinyxml2::XMLElement* root = xmlDoc.FirstChildElement();

    auto camera_ele = root->FirstChildElement("Camera");
    ParseCamera(camera_ele);

    auto opaque_ele = root->FirstChildElement("OpaqueItems");
    ParseOpaqueItems(opaque_ele);

    auto pass_list_ele = root->FirstChildElement("PassList");
    ParsePassList(pass_list_ele);

    auto instanced_ele = root->FirstChildElement("InstancedItems");
    if(instanced_ele) {
        ParseInstancedItem(instanced_ele);
    }
    lightList.emplace_back(50.0f, 50.0f, 0.0f);
}

void Scene::ParsePassList(tinyxml2::XMLElement *element)
{
    for (auto child = element->FirstChild(); child != nullptr; child = child->NextSibling())
    {
        const char * pass_name = nullptr;
        child->ToElement()->QueryAttribute("name", &pass_name);
        LOGI("ParsePassList %s", pass_name);
        passNameList.emplace_back(pass_name);
    }
}

void Scene::ParseCamera(tinyxml2::XMLElement * element)
{
    // 参数格式
    // <Camera position="0.0,0.0,0.0" lookat="0.0,0.0,-1.0" fov="60" aspect_ratio="0.462" zNear="0.1" zFar="1000.0">

    const char *position_str = nullptr;
    const char *lookat_str = nullptr;
    const char *fov_str=nullptr;
    const char *aspect_ratio_str=nullptr;
    const char *zNear_str=nullptr;
    const char *zFar_str=nullptr;

    element->QueryAttribute("position", &position_str);
    element->QueryAttribute("lookat", &lookat_str);
    element->QueryAttribute("fov", &fov_str);
    element->QueryAttribute("aspect_ratio", &aspect_ratio_str);
    element->QueryAttribute("zNear", &zNear_str);
    element->QueryAttribute("zFar", &zFar_str);

    std::vector<std::string> position_list;
    std::vector<std::string> lookat_list;

    glm::vec3 position;
    glm::vec3 look_at;
    float fov;
    float zNear;
    float zFar;
    float aspect_ratio;

    StringSplit(position_str, position_list, ",");
    StringSplit(lookat_str, lookat_list, ",");

    position.x = std::stof(position_list[0]);
    position.y = std::stof(position_list[1]);
    position.z = std::stof(position_list[2]);

    look_at.x = std::stof(lookat_list[0]);
    look_at.y = std::stof(lookat_list[1]);
    look_at.z = std::stof(lookat_list[2]);

    fov = std::stof(fov_str);
    aspect_ratio = std::stof(aspect_ratio_str);
    zNear = std::stof(zNear_str);
    zFar = std::stof(zFar_str);

    // float z_near, float z_far, float aspect_ratio, float fov
    mainCamera = std::make_shared<Camera> (zNear, zFar, aspect_ratio, 3.1416f / 180.0f * fov);
    mainCamera->SetPosition(position);
    mainCamera->SetForward(look_at - position);
    mainCamera->SetOnMobileInput();
}

void Scene::ParseOpaqueItems(tinyxml2::XMLElement * element)
{
    for (auto child = element->FirstChild(); child != nullptr; child = child->NextSibling())
    {
        /*
         * 格式
         * translation="0.0,3.0,0.0" rotation="0.0,0.0,0.0" scale="6.0,6.0,6.0" mesh_type="dummy" mesh_name="box"
         * */

        const char *translation_str = nullptr;
        const char *rotation_str = nullptr;
        const char *scale_str = nullptr;
        const char *mesh_type = nullptr;
        const char *mesh_name = nullptr;
        const char *texture_name = nullptr;

        child->ToElement()->QueryAttribute("translation", &translation_str);
        child->ToElement()->QueryAttribute("rotation", &rotation_str);
        child->ToElement()->QueryAttribute("scale", &scale_str);

        auto mesh_ele = child->FirstChildElement("Mesh");
        auto texture_ele = child->FirstChildElement("Texture");
        auto rb_ele = child->FirstChildElement("RigidBody");
        auto animation_ele = child->FirstChildElement("Animation");
        bool with_rb = (bool )(rb_ele);

        ParseMesh(mesh_ele, mesh_type, mesh_name);
        ParseTexture(texture_ele, texture_name);

        glm::vec3 translation;
        glm::vec3 rotation;
        glm::vec3 scale;

        std::vector<std::string> translation_list;
        std::vector<std::string> rotation_list;
        std::vector<std::string> scale_list;

        StringSplit(translation_str, translation_list, ",");
        StringSplit(rotation_str, rotation_list, ",");
        StringSplit(scale_str, scale_list, ",");

        translation.x = std::stof(translation_list[0]);
        translation.y = std::stof(translation_list[1]);
        translation.z = std::stof(translation_list[2]);

        rotation.x = std::stof(rotation_list[0]);
        rotation.y = std::stof(rotation_list[1]);
        rotation.z = std::stof(rotation_list[2]);

        scale.x = std::stof(scale_list[0]);
        scale.y = std::stof(scale_list[1]);
        scale.z = std::stof(scale_list[2]);

        std::shared_ptr<RenderItemOpaque<GraphicsAPI::GLESv3>> opaque_item = nullptr;

        if (!animation_ele) {
            opaque_item = std::make_shared<RenderItemStaticOpaque<GraphicsAPI::GLESv3>>();
        }
        else
        {
            opaque_item = std::make_shared<RenderItemSkinnedMeshOpaque<GraphicsAPI::GLESv3>>();
        }

        if (strcmp(mesh_type, "dummy") == 0)
        {
            if (strcmp(mesh_name, "box") == 0)
            {
                opaque_item->LoadDummyData(DummyItemType::Box, translation, rotation, scale, with_rb);
            }
            else if (strcmp(mesh_name, "plane") == 0)
            {
                opaque_item->LoadDummyData(DummyItemType::Plane, translation, rotation, scale, with_rb);
            }
            else if (strcmp(mesh_name, "sphere") == 0)
            {
                opaque_item->LoadDummyData(DummyItemType::Sphere, translation, rotation, scale, with_rb);
            }
            else if (strcmp(mesh_name, "icosahedron") == 0)
            {
                opaque_item->LoadDummyData(DummyItemType::Icosahedron, translation, rotation, scale, with_rb);
            }
            else
            {
                assert("not impl yet ... ");
            }
        }
        else if (strcmp(mesh_type, "mesh") == 0)
        {
            opaque_item->LoadMeshFileData(mesh_name, translation, rotation, scale);
        }

        std::shared_ptr<Texture2D<GraphicsAPI::GLESv3>> texture_test_box = std::make_shared<Texture2D<GraphicsAPI::GLESv3>>(texture_name, 0);
        opaque_item->AppendTexture(texture_test_box);

        if (!animation_ele) {
            opaqueItemList.emplace_back(opaque_item);
        } else{
            skinnedMeshItemList.emplace_back(opaque_item);
        }

        if (with_rb)
        {
            const char * mass_str = nullptr;
            const char * force_str = nullptr;
            const char * torque_str = nullptr;

            rb_ele->QueryAttribute("mass", &mass_str);

            // 静态物体，质量无限大
            if (mass_str[0] == 'i' && mass_str[1] == 'n' && mass_str[2] == 'f')
            {
                opaque_item->GetModel()->GetRB()->SetStatic();
            }

            // 有数值质量
            else
            {
                rb_ele->QueryAttribute("force", &force_str);
                rb_ele->QueryAttribute("torque", &torque_str);

                float mass = std::stof(mass_str);

                std::vector<std::string> force_list;
                std::vector<std::string> torque_list;
                StringSplit(force_str, force_list, ",");
                StringSplit(torque_str, torque_list, ",");

                opaque_item->GetModel()->GetRB()->SetMass(mass);
                opaque_item->GetModel()->GetRB()->SetForce(
                        glm::vec3(std::stof(force_list[0]), std::stof(force_list[1]),
                                  std::stof(force_list[2])));
                opaque_item->GetModel()->GetRB()->SetTorque(
                        glm::vec3(std::stof(torque_list[0]), std::stof(torque_list[1]),
                                  std::stof(torque_list[2])));
            }

            phyScene->AddRigidBody(opaque_item->GetModel()->GetRB());
        }
    }
}

void Scene::ParseInstancedItem(tinyxml2::XMLElement * element)
{
    for (auto child = element->FirstChild(); child != nullptr; child = child->NextSibling())
    {

        const char *mesh_type = nullptr;
        const char *mesh_name = nullptr;
        const char *texture_name = nullptr;
        const char * number_str = nullptr;
        const char * bounding_box_str = nullptr;
        const char * distribute_type = nullptr;

        auto mesh_ele = child->FirstChildElement("Mesh");
        auto texture_ele = child->FirstChildElement("Texture");
        auto distribute_ele = child->FirstChildElement("Distribute");
        auto particle_ele = child->FirstChildElement("ParticleSystem");

        ParseMesh(mesh_ele, mesh_type, mesh_name);

        if (texture_ele) {
            ParseTexture(texture_ele, texture_name);
        }

        distribute_ele->QueryAttribute("type", &distribute_type);

        DummyItemType dummy_type;
        if (strcmp(mesh_name, "box") == 0)
        {
            dummy_type = DummyItemType::Box;
        }
        else if (strcmp(mesh_name, "plane") == 0)
        {
            dummy_type = DummyItemType::Plane;
        }
        else if (strcmp(mesh_name, "sphere") == 0)
        {
            dummy_type = DummyItemType::Sphere;
        }
        else if (strcmp(mesh_name, "icosahedron") == 0)
        {
            dummy_type = DummyItemType::Icosahedron;
        }
        else
        {
            assert("not impl yet ... ");
        }

        std::shared_ptr<RenderItemInstanced<GraphicsAPI::GLESv3>> instanced_item = nullptr;

        if (strcmp(distribute_type, "random") == 0) {

            distribute_ele->QueryAttribute("number", &number_str);
            distribute_ele->QueryAttribute("bounding_box", &bounding_box_str);

            Phy::AABB bounding_box{};

            std::vector<std::string> bounding_box_list;
            StringSplit(bounding_box_str, bounding_box_list, ",");

            bounding_box.minPoint.x = std::stof(bounding_box_list[0]);
            bounding_box.maxPoint.x = std::stof(bounding_box_list[1]);
            bounding_box.minPoint.y = std::stof(bounding_box_list[2]);
            bounding_box.maxPoint.y = std::stof(bounding_box_list[3]);
            bounding_box.minPoint.z = std::stof(bounding_box_list[4]);
            bounding_box.maxPoint.z = std::stof(bounding_box_list[5]);
            int instance_number = std::stoi(number_str);
            instanced_item = std::make_shared<RenderItemInstanced<GraphicsAPI::GLESv3>>(instance_number, bounding_box, dummy_type);
        }
        else if (strcmp(distribute_type, "evenly") == 0)
        {
            instanced_item = std::make_shared<RenderItemInstanced<GraphicsAPI::GLESv3>>(128, 10, 64, 0.1f, glm::vec3(10.0f, 0.0f, 10.0f), dummy_type);
        }

        std::shared_ptr<Texture2D<GraphicsAPI::GLESv3>> texture_test_box = std::make_shared<Texture2D<GraphicsAPI::GLESv3>>(texture_name, 0);
        instanced_item->AppendTexture(texture_test_box);
        instanceItemList.emplace_back(instanced_item);
        if (particle_ele) {
            particleSystem->AddParticle(instanced_item);
        }
    }
}


void Scene::Update(double elapsed_time, double tot_time)
{
    tick_elapsed_time = elapsed_time;

    lightList[0].x = 50.0f * (float) cos(tot_time * 0.5f);
    lightList[0].y = 50.0f;
    lightList[0].z = 50.0f * (float) sin(tot_time * 0.5f);

    skyboxItem->GetModel()->SetTranslation(mainCamera->GetPosition());

    // 更新物理场景
    phyScene->Update(elapsed_time, tot_time);

    // 更新渲染场景
    for (auto & rb : phyScene->rb_list)
    {
        auto t_mat = rb->GetTranslationMat();
        auto r_mat = rb->GetRotateMat();

        rb->GetModel()->SetTranslation(t_mat, false);
        rb->GetModel()->SetRotation(r_mat, false);
    }

    for (auto & item : skinnedMeshItemList)
    {
        auto skinned_ptr = std::dynamic_pointer_cast<RenderItemSkinnedMeshOpaque<GraphicsAPI::GLESv3>>(item);
        auto skinned_mesh_model = std::dynamic_pointer_cast<SkinnedMeshModel>(skinned_ptr->GetModel());
        skinned_mesh_model->Update(elapsed_time, tot_time);
    }
}

std::shared_ptr<IRenderItem> Scene::GetSkyboxItem()
{
    return skyboxItem;
}

void Scene::SetSkyboxItem(std::shared_ptr<IRenderItem> item)
{
    skyboxItem = std::move(item);
}


glm::vec3 Scene::GetLightPos(int index) const
{
    return lightList[index];
}

glm::mat4 Scene::GetLightMatrix(int index) const
{
    glm::vec3 light_pos = lightList[index];
    glm::vec3 look_at_point = glm::vec3(0, 0, 0);
    glm::vec3 forward = look_at_point - light_pos;
    glm::vec3 right = glm::cross(look_at_point, glm::vec3(0, 1.0f, 0));

    if (glm::length(right) < 0.0001f)
    {
        right = glm::vec3(1.0f, 0.0f, 0.0f);
    }

    glm::vec3 up = glm::cross(right, forward);
    up = glm::normalize(up);

    glm::mat4 light_view_mat = glm::lookAt(light_pos, look_at_point, up);

    glm::mat4 light_proj_mat = glm::perspective(glm::radians( 60.0f), 1.0f, 1.0f, 500.0f);
    return light_proj_mat * light_view_mat;
}

std::vector<std::shared_ptr<IRenderItem>>::const_iterator Scene::GetOpaqueItemBegin() const
{
    return opaqueItemList.cbegin();
}

std::vector<std::shared_ptr<IRenderItem>>::const_iterator Scene::GetOpaqueItemEnd() const
{
    return opaqueItemList.cend();
}

std::vector<std::shared_ptr<IRenderItem>>::const_iterator Scene::GetInstancedItemBegin() const
{
    return instanceItemList.cbegin();
}

std::vector<std::shared_ptr<IRenderItem>>::const_iterator Scene::GetInstancedItemEnd() const
{
    return instanceItemList.cend();
}

std::vector<std::shared_ptr<IRenderItem>>::const_iterator Scene::GetSkinnedMeshItemBegin() const
{
    return skinnedMeshItemList.cbegin();
}

std::vector<std::shared_ptr<IRenderItem>>::const_iterator Scene::GetSkinnedMeshItemEnd() const
{
    return skinnedMeshItemList.cend();
}

void Scene::ParseMesh(tinyxml2::XMLElement * element, const char * & mesh_type, const char * & mesh_name)
{
    element->QueryAttribute("mesh_type", &mesh_type);
    element->QueryAttribute("mesh_name", &mesh_name);
}

void Scene::ParseTexture(tinyxml2::XMLElement * element, const char * & texture_name)
{
    element->QueryAttribute("texture", &texture_name);
}

std::vector<std::string>::const_iterator Scene::GetPassListBegin() const
{
    return passNameList.cbegin();
}

std::vector<std::string>::const_iterator Scene::GetPassListEnd() const
{
    return passNameList.cend();
}

//////////////////////////////////////////////

std::shared_ptr<Scene> SceneMgr::GetCurrentScene()
{
    return currentScene;
}


void SceneMgr::LoadScene(const char * scene_file_path)
{
    currentScene = std::make_shared<Scene> (scene_file_path);
}

std::shared_ptr<Camera> Scene::GetMainCamera() const{
    return mainCamera;
}
